﻿using System;
using System.Collections.Generic;
using System.Text;
using Common.Logging;
using System.Reflection;
using System.IO;

namespace DotNetServiceLib
{
    public class MethodActivator
    {
        private static readonly ILog log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
        private Assembly _assembly = null;
        private bool _loggingIsEnabled = false;
        private bool _methodIsStatic = false;

        public MethodActivator() { }

        public bool LoggingIsEnabled
        {
            set
            {
                _loggingIsEnabled = value;
            }
        }

        public bool MethodIsStatic
        {
            set
            {
                _methodIsStatic = value;
            }
        }

        public bool Fire(string assemblyLocation, string className, string methodName, List<KeyValuePair<string, string>> listOfArguments)
        {
            object returnValue = new object();
            return Fire(assemblyLocation, className, methodName, listOfArguments, ref returnValue);
        }
        public bool Fire(string assemblyLocation, string className, string methodName, List<KeyValuePair<string, string>> listOfArguments, ref object returnValue)
        {
            bool isSuccess = true;
            try
            {
                // If no absolute path was provided, we look for the DLL inside 
                // the current directory (where the application is executed from).
                if(!File.Exists(assemblyLocation))
                    assemblyLocation = AppDomain.CurrentDomain.BaseDirectory + assemblyLocation;
                _assembly = Assembly.LoadFrom(assemblyLocation);
            }
            catch (FileNotFoundException fileNotFound)
            {
                log.Error(String.Format("Assembly could not be found at {0}.", assemblyLocation));
                log.Error(fileNotFound.ToString());
                isSuccess = false;
            }
            catch (ArgumentException argumentExcp)
            {
                log.Error("No Assembly location was provided.");
                log.Error(argumentExcp.ToString());
                isSuccess = false;
            }

            bool classWasFound = false;
            if (isSuccess && null != _assembly)
            {
                Type type = null;
                object taskInstance = null;
                if (!_methodIsStatic) // instance of the class must only be created for non static methods 
                    type = GetClassInstance(className, ref taskInstance);
                if ((type != null && taskInstance != null) || _methodIsStatic)
                {
                    classWasFound = true;
                    MethodInfo methodInfo;
                    Type[] paramTypes;
                    int j = 0;
                    if (listOfArguments.Count > 0)
                    {
                        paramTypes = new Type[listOfArguments.Count];
                        foreach (KeyValuePair<string, string> kvp in listOfArguments)
                        {
                            if (Type.GetType(kvp.Key) != null)
                            {
                                paramTypes[j++] = Type.GetType(kvp.Key);
                            }
                            else
                            {
                                isSuccess = false;
                                log.Error(String.Format("Argument '{0} - {1}' was not recognized.", kvp.Key, kvp.Value));
                            }
                        }
                        methodInfo = type.GetMethod(methodName, paramTypes);
                    }
                    else
                    {
                        methodInfo = type.GetMethod(methodName);
                    }

                    if (null == methodInfo || !isSuccess)
                    {
                        log.Error(String.Format("No matching method with name '{0}' could be found.", methodName));
                    }
                    else
                    {
                        // Dynamically Invoke the Object
                        object[] arguments = new object[listOfArguments.Count];
                        int k = 0;
                        foreach (KeyValuePair<string, string> kvp in listOfArguments)
                            arguments[k++] = ArgumentParser.Try(kvp.Value, kvp.Key);
                        try
                        {
                            returnValue = methodInfo.Invoke(taskInstance, arguments);
                            if (_loggingIsEnabled)
                            {
                                log.Info(String.Format("The method {0} was succesfully invoked.", methodName));
                                if (returnValue != null || returnValue == "")
                                    log.Info(String.Format("Returned object: {0}.", returnValue));
                                else
                                    log.Info("No returned object was received.");
                            }
                        }
                        catch (TargetParameterCountException parameterCountExc)
                        {
                            log.Error("Parameters provided do not match method's signature.");
                            log.Error(parameterCountExc.ToString());
                        }
                        catch (ArgumentException argumentExc)
                        {
                            log.Error("One or more parameter could not be parsed succesfully.");
                            log.Error(argumentExc.ToString());
                        }
                        catch (TargetException targetExc)
                        {
                            log.Error("Method could not be called succesfully. No instance was created for the instance method.");
                            log.Error(targetExc.ToString());
                        }
                        catch (TargetInvocationException targetInvoExc)
                        {
                            log.Error("Target method could not be executed succesfully.");
                            log.Error(targetInvoExc.ToString());
                        }
                    }
                }
            }
            if (!classWasFound) {
                log.Error(String.Format("A class with the name '{0}' could not be found. Don't forget to specify the class's fullname (for example: System.IO.Directory).", className)); 
            }
            return isSuccess;
        }

        private Type GetClassInstance(string className, ref object taskInstance)
        {
            Type typeToReturn = null;
            foreach (Type type in _assembly.GetTypes())
            {
                // class was found inside Assembly
                if (type.IsClass == true)
                {
                    if (type.FullName == className)
                    {
                        if (_loggingIsEnabled)
                            log.Info(String.Format("Found class: {0}", type.Name));
                        try
                        {
                            taskInstance = Activator.CreateInstance(type);
                            if (_loggingIsEnabled)
                                log.Info("Instance of class was succesfully created.");
                            typeToReturn = type;
                        }
                        catch (TargetInvocationException invocationExcp)
                        {
                            log.Error(String.Format("Class '{0}' could no be loaded. Please review the inner exception.", type.Name));
                            log.Error(invocationExcp.ToString());
                        }
                        break;
                    }
                }
            }
            return typeToReturn;
        }

        public List<KeyValuePair<string, string>> ProcessArguments(string argumentsFromConfiguration)
        {
            
            List<KeyValuePair<string, string>> arguments = new List<KeyValuePair<string, string>>();
            if (argumentsFromConfiguration.Contains(""))
            {
                string[] args = argumentsFromConfiguration.Split(new char[] { ',' });
                foreach (string s in args)
                {
                    AddKeyValuePair(s, ref arguments);
                }
            }
            else
            {
                AddKeyValuePair(argumentsFromConfiguration, ref arguments);
            }
            return arguments;
        }

        private void AddKeyValuePair(string arg, ref List<KeyValuePair<string, string>> arguments)
        {
            int firstPipePosition = 0;
            string type = "", value = "";
            int j = 0;
            char[] argChar = arg.ToCharArray();
            foreach (char c in arg)
            {
                if (c == '|')
                {
                    firstPipePosition = j;
                    break;
                }
                ++j;
            }
            if (firstPipePosition > 0 && firstPipePosition < arg.Length)
            {
                type = arg.Substring(0, firstPipePosition).Trim();
                value = arg.Substring(++firstPipePosition, arg.Length - type.Length - 1).Trim();
                // check for integrity of type before adding it to the collection of arguments
                if (Type.GetType(type) != null)
                    arguments.Add(new KeyValuePair<string, string>(type, value));
                else
                    log.Error(String.Format("Following type could not be loaded: {0}", type));
            }
        }
    }
}
